這天小明問我說
我這個API 方法會回傳錯誤代碼, 呼叫端要處理這些各種不同的錯誤代碼,
可以幫忙看看這些程式碼有沒有沒考慮到的地方?
許多人沒有使用異常處理的藉口有很多, 但是大多數歸結為兩種看法:
我們看看下面的示範程式碼:
switch( checkLogin() ){
case -1:
//Invalid credentials
...
break;
case -2:
//Too many login attempts
...
break;
default:
// Successful
break;
}
以上程式碼有兩個問題
您可以採用另一種方法來解決前面所述的2個主要問題
switch( checkLogin() ){
case ErrorCode.INVALID_LOGIN_CREDENTIALS:
...
break;
case ErrorCode.TOO_MANY_LOGIN_ATTEMPTS:
...
break;
default:
// Successful scenario, log in the user
break;
}
但是如果我們想在 checkLogin 方法中重構一個 Extract 方法, 那將是很困難的工作: 我們必須從 checkLogin 方法中攜帶錯誤代碼, 在 Extract 方法中有用到的錯誤代碼必須將其傳回去. 為了在我們的應用程序的外層中委派處理這種特殊情況的邏輯, 我們可能需要更大的靈活性.
例如以下 checkLogin 程式碼, 雖然用了 enum 方式取代了錯誤代碼的魔法數字
private ErrorCode checkLogin() {
...
if ( hasNotValidCredentials ) {
return ErrorCode.INVALID_LOGIN_CREDENTIALS;
}
...
if ( hasTooManyLoginAttempts ) {
return ErrorCode.TOO_MANY_LOGIN_ATTEMPTS;
}
return ErrorCode.LOGIN_SUCCESSFUL;
}
當我們想要嘗試 Extract 這一段程式碼
if ( hasTooManyLoginAttempts ) {
return ErrorCode.TOO_MANY_LOGIN_ATTEMPTS;
}
重構變成下面程式碼...
private ErrorCode Extract() {
if ( hasTooManyLoginAttempts ) {
return ErrorCode.TOO_MANY_LOGIN_ATTEMPTS;
}
???
}
進行到這裡, 你就會發現這還得花更大的力氣才能往下做...
考慮到前面描述的問題, 在這種情況下唯一可以幫助的是用 "丟例外錯誤" 替換 "錯誤代碼"
private void checkLogin() {
...
if ( hasNotValidCredentials ) {
throw new InvalidLoginCreadentialsException();
}
...
if ( hasTooManyLoginAttempts ) {
return new TooManyLoginAtteptsException();
}
}
然後你就可以輕鬆的進行重構(Refactoring), 變成下面程式碼
private void checkLogin()
{
checkLoginCredentials();
checkLoginAttempts();
checkBannedUser();
}
在重構過程中(Refactoring), 完全根本不需要考慮回傳值的問題
如果將異常用於經常失敗的代碼, 則程式碼執行的性能將是不可接受的. 這是一個的確令人擔憂的問題. 當程式碼拋出異常時, 其性能可能會降低幾個數量級. 但是在嚴格遵守不允許使用錯誤代碼的例外處理準則的同時, 我們也可以獲得良好的性能. 有兩種建議可以解決這個問題.
有時候將發生例外的方法內容可以拆成兩部分, 這可以提高其性能. 例如下面程式碼示範:
讓我們看一下Dictionary 類的indexed 屬性.
var table = new Dictionary<string,int>();
...
int value = table["key"];
如果table 字典中不存在該鍵值(Key), 則索引器將引發例外錯誤. 在這段程式碼經常執行失敗的情況下, 這會導致引起執行性能問題(Performance Problem). 緩解問題的方法之一是在訪問鍵值之前測試鍵是否在字典中.
var table = new Dictionary<string,int>();
...
if( table.Contains("key") ){
int value = table["key"];
}
在上面的示例中, 包含條件的用於測試條件的成員稱為"測試者".
if( table.Contains("key") )
用於執行潛在發生例外的成員(索引器) 稱為"執行者".
int value = table["key"];
對於性能要求極高的API , 應使用Tester-Doer 更快的模式.
例如DateTime 定義了一個Parse 方法, 該方法在字符串解析失敗時拋出例外. 但它還定義了一個相應的TryParse 方法, 該方法嘗試進行解析, 但是如果解析失敗則返回false, 並使用out 參數返回成功解析的結果.
使用此模式時, 在"try" 的功能中, 如果嘗試了所有方法無效, 最後仍然失敗時, 則該方法仍必須拋出例外.